%%%-------------------------------------------------------------------
%%% @author mirahs(2892727493@qq.com)
%%% @doc
%%%     网络通讯协议处理
%%% @end
%%%-------------------------------------------------------------------
-module(net_protocol_handler).

-export([
    head_size/0

    ,on_accept/1
    ,on_head/1
    ,on_packet/3
    ,on_exit/1
    ,on_close/2
]).

-include("common.hrl").
-include("net.hrl").
-include("env.hrl").


%%%===================================================================
%%% API functions
%%%===================================================================

head_size() ->
    4.

on_accept(Socket) ->
    case gen_tcp:recv(Socket, 23, 10000) of
        {ok, ?NET_CLIENT_GAME} ->
            create_conn(game, Socket);
        {ok, ?NET_CLIENT_WEB} ->
            web_handle(Socket);
        {ok, ?NET_CLIENT_TESTER} ->
            create_conn(tester, Socket);
        {error, Reason} ->
            Peer = inet:peernames(Socket),
            Sock = inet:socknames(Socket),
            ?ERR("获取客户端类型失败 Reaspon:~w,Peer:~w,Sock:~w", [Reason, Peer, Sock]),
            gen_tcp:close(Socket)
    end.

on_head(<<BodyLen:16/big-integer-unsigned, _Cmd:16/big-integer-unsigned>>) ->
    {ok, BodyLen};
on_head(_Bin) ->
    {error, head_data_error}.

on_packet(<<_BodyLen:16/big-integer-unsigned, Cmd:16/big-integer-unsigned>>, BodyBin, State) ->
    routing(Cmd, BodyBin, State).

%% 控制对象异常退出
on_exit(ObjectId) ->
    role_online_mgr:leave(ObjectId).

%% 网络连接退出
on_close(Object, ObjectPid) ->
    Object:disconnect(ObjectPid).


%%%===================================================================
%%% Internal functions
%%%===================================================================

create_conn(ClientType, Socket) ->
    {ok, Pid} = net_tcp_conn:start(?MODULE, ClientType, Socket),
    ok = gen_tcp:controlling_process(Socket, Pid),
    ?DEBUG("建立新连接(~w)", [ClientType]).

web_handle(Socket) ->
    case gen_tcp:recv(Socket, 2, 3000) of
        {ok, <<Len:16/big-integer-unsigned>>} ->
            case gen_tcp:recv(Socket, Len, 5000) of
                {ok, BinData} ->
                    ?DEBUG("web_handle 客户端发送过来的数据:~ts", [BinData]),
                    try
                        {ok, {Module, Func, Args, Time, Sign}} = util_type:bitstring_to_term(BinData),

                        Handler = env_mgr:get(?ENV_NET_WEB_HANDLER),
                        Result = Handler:handle(Module, Func, Args, Time, Sign),
                        LenBin = byte_size(Result),
                        gen_tcp:send(Socket, <<LenBin:16/big-integer-unsigned, Result/binary>>)
                    catch _Type:_Reason:Stacktrace ->
                        ?ERR("call_data invalid Type:~w,Reason:~w,Stacktrace:~n~p", [_Type, _Reason, Stacktrace]),
                        Result2 = <<"{\"code\":1, \"msg\":\"call_data invalid\"}">>,
                        LenBin2 = byte_size(Result2),
                        gen_tcp:send(Socket, <<LenBin2:16/big-integer-unsigned, Result2/binary>>)
                    end;
                {error, Reason} ->
                    ?ERR("web_handle 读取包体失败:~w", [Reason])
            end;
        Other ->
            ?ERR("web_handle 读取包头失败[~p]", [Other])
    end,
    gen_tcp:close(Socket).


routing(Cmd, Bin, State = #net_conn{account = OAccount, bad_req_count = BadReq}) ->
    case mapping:module(game_server, Cmd) of
        {ok, true, Caller, Proto, Mod} when OAccount =/= <<>> ->
            call(Caller, Proto, Mod, Cmd, Bin, State);
        {ok, false, Caller, Proto, Mod} ->
            call(Caller, Proto, Mod, Cmd, Bin, State);
        _ ->
            ?ERR("帐号[~s]发送了无效请求[~w]", [OAccount, Cmd]),
            {next, State#net_conn{bad_req_count = BadReq + 1}}
    end.

call(Caller, Proto, Mod, Cmd, Bin, State) ->
    case catch Proto:unpack(Cmd, Bin) of
        {ok, Data} ->
            do_call(Caller, Mod, Cmd, Data, State);
        Error ->
            ?ERR("解包数据出错[Cmd:~w,Proto:~w,Mod:~w,Err:~w]:~w", [Cmd, Proto, Mod, Error, Bin]),
            {next, State}
    end.

%% 由 connector 执行调用
do_call(connector, Mod, Cmd, Data, State = #net_conn{account = OAccount}) ->
    case catch Mod:handle(Cmd, Data, State) of
        ok ->
            {next, State};
        {ok, NewState} when is_record(NewState, net_conn) ->
            {next, NewState};
        {reply, Cmd, Reply} ->
            util_net:pack_send(self(), Cmd, Reply),
            {next, State};
        {reply, Cmd, Reply, NewState} when is_record(NewState, net_conn) ->
            util_net:pack_send(self(), Cmd, Reply),
            {next, NewState};
        {stop, _Reason} ->
            ?DEBUG("协议[~w]返回失败:~ts", [Cmd, _Reason]),
            stop;
        {error, Reason} ->
            ?ERR("帐号[~s]处理命令出错:~w [Cmd:~w,Data:~w]", [OAccount, Reason, Cmd, Data]),
            {next, State};
        Reason ->
            ?ERR("帐号[~s]处理命令未知错误[Mod:~w,Cmd:~w,Data:~w,Reason:~n~p]", [OAccount, Mod, Cmd, Data, Reason]),
            {next, State}
    end;

%% 过滤无效请求
do_call(object, _Mod, _Cmd, _Data, State = #net_conn{object = undefined}) -> {next, State};
do_call(object, _Mod, _Cmd, _Data, State = #net_conn{object_pid = undefined}) -> {next, State};

%% 由 object 执行调用
do_call(object, Mod, Cmd, Data, State = #net_conn{object = Object, object_pid = ObjectPid}) ->
    Object:rpc(ObjectPid, Mod, Cmd, Data),
    % 此处不能直接 next，必须等调用者通知后才读取下一个包，否则容易被攻击
    {ok, State};

%% 错误的调用发起者
do_call(Caller, Mod, Cmd, _Data, State) ->
    ?ERR("不支持的调用发起者[Caller:~w, Mod:~w, Cmd:~w]，请检查mapping.erl设置", [Caller, Mod, Cmd]),
    {next, State}.
